package com.chentf.reggie.thread.threadLocal;

import ch.qos.logback.classic.util.LoggerNameUtil;
import com.alibaba.druid.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * @author chentengfei
 * @desc ...
 * @date 2023-03-01 13:00:34
 */
public class ThreadLocalTest {

    private static Logger logger = LoggerFactory.getLogger(ThreadLocalTest.class);

    /****
     * 1.ThreadLocal可以实现[资源对象]的线程隔离,让每个线程各用各的[资源对象],避免争用引发的线程安全问题
     * 2.ThreadLocal同时实现了线程内的资源共享问题
     * 3.其原理是,每个线程内有一个ThreadLocalMap类型的成员变量,用来存储资源对象
     *  <1>.调用set方法,就是以ThreadLocal自己作为key,资源对象作为value,放到当前线程的ThreadLocalMap集合中
     *  <2>.调用get方法,就是以ThreadLocal自己作为key,到当前线程中查找关联的资源值
     *  <3>.递交用remove方法,就是以ThreadLocal自己作为key,移除当前线程关联的资源值
     * 4.为什么ThreadLocalMap中的key(即ThreadLocal)要涉及成弱引用?
     *  (1).Thread可能需要长时间运行(如线程池中的线程),如果key不在使用,需要在内存不足(GC)时释放其占用的内存
     *  (2).但GC仅是让key的内存释放,后续还要根据key是否为null来进一步释放值的内存,释放时机有
     *      a).获取key发现null key
     *      b).set key时,会使用启发式扫描,清除临近的null key,启发次数与元素个数,是否发现null key有关.
     *      c).remove时(推荐),因为一般使用ThreadLocal时都把它作为静态变量,因此GC无法回收.
     */

    public static void main(String[] args) {
        //使用ThreadLocal获取的连接对象每次都是不一样的
        test01();
        //每次获取的资源对象都是同一个
//        test02();
    }

    private static void test02() {
        for (int i = 0; i < 2; i++) {
            new Thread(() ->{
                logger.info(Utils.getConnection().toString());
                logger.info(Utils.getConnection().toString());
                logger.info(Utils.getConnection().toString());
            },"t" + (i + 1)).start();
        }
    }


    private static void test01(){
        for (int i = 0; i < 5; i++) {
            new Thread(() ->{
                Connection connection = Utils.getConnection();
                logger.debug(connection.toString());
            },"t" + (i + 1)).start();
        }
    }

    static class Utils{
        private static final ThreadLocal<Connection> t1 = new ThreadLocal<>();

        public static Connection getConnection(){
            //到当前线程获取这个资源对象
            Connection connection = t1.get();
            if(connection == null){
                connection = innerGetConnection();
                //将资源存入当前线程
                t1.set(connection);
            }
            t1.remove();
            return connection;
        }

        private static Connection innerGetConnection() {
            try {
                return DriverManager.getConnection("jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true", "root","admin");
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return null;

        }
    }
}
